/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.cef.generator.repository.entity.methods;

import com.inspur.edp.caf.generator.baseInfo.ParameterInfo;
import com.inspur.edp.caf.generator.baseInfo.TypeInfo;
import com.inspur.edp.caf.generator.method.ClassMethodGenerator;
import com.inspur.edp.cef.api.repository.GspDbDataType;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.element.GspElementObjectType;
import com.inspur.edp.cef.gencommon.MediateType;
import com.inspur.edp.cef.generator.repository.base.assemblerGenerator.DataTypeAssemblerGenCtx;
import com.inspur.edp.cef.repository.typetransprocesser.*;
import com.inspur.edp.das.commonmodel.IGspCommonElement;
import com.inspur.edp.das.commonmodel.IGspCommonObject;
import com.inspur.edp.udt.designtime.api.entity.ComplexDataTypeDef;
import com.inspur.edp.udt.designtime.api.entity.SimpleDataTypeDef;
import com.inspur.edp.udt.designtime.api.entity.UnifiedDataTypeDef;
import com.inspur.edp.udt.designtime.api.entity.dbInfo.ColumnMapType;
import com.inspur.edp.udt.designtime.api.entity.element.UdtElement;
import io.iec.edp.caf.databaseobject.api.entity.DataType;
import io.iec.edp.caf.databaseobject.api.entity.DatabaseObjectColumn;
import io.iec.edp.caf.databaseobject.api.entity.DatabaseObjectTableCore;
import lombok.var;
import org.eclipse.jdt.core.dom.*;

import java.util.ArrayList;
import java.util.Arrays;

public abstract class InitColumnInfoMethodGenerator extends ClassMethodGenerator {
    protected DataTypeAssemblerGenCtx assGenCtx;

    public InitColumnInfoMethodGenerator(DataTypeAssemblerGenCtx assGenCtx) {
        this.assGenCtx = assGenCtx;
    }

    protected abstract DatabaseObjectTableCore getDatabaseObject();

    @Override
    protected boolean getIsOverride() {
        return true;
    }

    @Override
    protected Block buildMethodBody() {
        ArrayList<Statement> statements = new ArrayList<Statement>();
        var elementList = assGenCtx.getDataType().getContainElements();
        DatabaseObjectTableCore dbo = getDatabaseObject();
        for (IGspCommonField element : elementList) {
            if (element.getIsVirtual() || element.getObjectType() == GspElementObjectType.DynamicProp) {
                continue;
            }
            if (element.getIsUdt()) {
                buildUdtElementStatement((IGspCommonElement) element, dbo, statements);
            } else {
                var column = dbo.getColumnById(((IGspCommonElement) element).getColumnID());
                if (column == null) {
                    throw new RuntimeException("dbo中找不到" + element.getLabelID() + "对应的列");
                }
                Statement statement = buildAddColumnStatement((IGspCommonElement) element, column);
                statements.add(statement);
            }
        }

        var block = ast.newBlock();
        block.statements().addAll(statements);
        return block;
    }

    private void buildUdtElementStatement(
            IGspCommonElement element,
            DatabaseObjectTableCore dbo,
            ArrayList<Statement> statements) {

        UnifiedDataTypeDef udt=(UnifiedDataTypeDef)(
                (assGenCtx.getRepoGenCtx().getCefGenCtx().getMetadataJitContext().getMetadata(element.getUdtID()).getContent() instanceof  UnifiedDataTypeDef)
                        ? assGenCtx.getRepoGenCtx().getCefGenCtx().
                        getMetadataJitContext().
                        getMetadata(element.getUdtID()).getContent()
                        :null);
        if (udt instanceof SimpleDataTypeDef || (udt instanceof ComplexDataTypeDef && ((ComplexDataTypeDef)udt).getDbInfo().getMappingType() == ColumnMapType.SingleColumn)){
            var column = dbo.getColumnById(element.getColumnID());
            if (column == null) {
                throw new RuntimeException("dbo中找不到" + element.getLabelID() + "对应的列");
            }
            Statement statement = buildAddColumnStatement(element, column);
            statements.add(statement);
            return;
        }

        for (var childElement : element.getChildElements()) {

            var childEle =
                (IGspCommonElement) ((childElement instanceof IGspCommonElement) ? childElement : null);

            var column = dbo.getColumnById(childEle.getColumnID());
            if (column == null) {
                throw new RuntimeException("dbo中找不到" + childEle.getLabelID() + "对应的列");
            }
            // TODO 暂时没法只根据element判断是否是ChildElement
            childEle.setIsUdt(true);
            Statement statement = buildAddChildColumnStatement(childEle, element, column,(ComplexDataTypeDef) udt);
            statements.add(statement);
        }
    }

    private Statement buildAddChildColumnStatement(
        IGspCommonElement element, IGspCommonElement belongElement, DatabaseObjectColumn dboColumn,ComplexDataTypeDef complexUdt) {

      UdtElement udtElement=(UdtElement) complexUdt.findElement(belongElement.getMappingRelation().getMappingInfo(element.getID()));
      if(udtElement==null){
        throw new RuntimeException("没有找到多值存为多列udt上"+element.getLabelID()+"的字段！");
      }
      ArrayList<Expression> arguments =
          buildArgumentList(element, belongElement, dboColumn,true,udtElement);
      var invocation = ast.newSuperMethodInvocation();
      invocation.setName(ast.newSimpleName("addColumn"));
      invocation.arguments().addAll(arguments);
      return ast.newExpressionStatement(invocation);
    }

    private ExpressionStatement buildAddColumnStatement(
            IGspCommonElement element, DatabaseObjectColumn dboColumn) {
      ArrayList<Expression> arguments =
                buildArgumentList(element, element, dboColumn,false,null);

        var invocation = ast.newSuperMethodInvocation();
        invocation.setName(ast.newSimpleName("addColumn"));
        invocation.arguments().addAll(arguments);
        return ast.newExpressionStatement(invocation);
    }

    private boolean isPrimaryKey(IGspCommonElement element) {
        return ((IGspCommonObject) assGenCtx.getDataType()).getColumnGenerateID().getElementID() .equals( element.getID());
    }

    private boolean isParentColumn(IGspCommonElement element) {
        if (element.getBelongObject() != null && element.getBelongObject().getParentObject() != null) {
            return element.getBelongObject().getKeys().get(0).getSourceElement() .equals( element.getID());
        }
        return false;
    }

    private ArrayList<Expression> buildArgumentList(
            IGspCommonElement element, IGspCommonElement belongElement, DatabaseObjectColumn dboColumn,boolean isMultiColumns,UdtElement field) {

      ArrayList<Expression> argumentList =
          new ArrayList<Expression>();

      var paramLabelId = ast.newStringLiteral();
      paramLabelId.setLiteralValue(element.getLabelID());
      argumentList.add(paramLabelId);

      var paramColumnId = ast.newStringLiteral();
      paramColumnId.setLiteralValue(element.getLabelID());
      argumentList.add(paramColumnId);

      var dataType = transDataType(dboColumn.getDataType());

      Expression paramProcesser;
      if (isMultiColumns && field.getObjectType() == GspElementObjectType.Enum) {
        MediateType enumType = new MediateType(
            field.getBelongObject().getGeneratedEntityClassInfo().getClassNamespace(),
            field.getEnumTypeName());
        paramProcesser = getEnumTypeProcesser(enumType);
      } else if (element.getObjectType() == GspElementObjectType.Enum) {
        MediateType enumType = new MediateType(
            element.getBelongObject().getGeneratedEntityClassInfo().getClassNamespace(),
            element.getEnumTypeName());
        paramProcesser = getEnumTypeProcesser(enumType);
      } else {
        Class processer = getTypeProcesser(dataType, element);
        var invocation = ast.newMethodInvocation();
        invocation.setExpression(ast.newName(processer.getName()));
        invocation.setName(ast.newSimpleName("getInstacne"));
        paramProcesser = invocation;
      }
      // ArgumentUtils.getPropertyArgument(processer, "Instacne");

        argumentList.add(paramProcesser);


        var paramPrimary = ast.newBooleanLiteral(isPrimaryKey(element));
        argumentList.add(paramPrimary);

        var prarmMulti = ast.newBooleanLiteral(element.getIsMultiLanguage());
        argumentList.add(prarmMulti);

        var paramUdt = ast.newBooleanLiteral(element.getIsUdt());
        argumentList.add(paramUdt);

        var paramAsso = ast.newBooleanLiteral(element.getObjectType() == GspElementObjectType.Association);
        argumentList.add(paramAsso);

        var paramEnum = ast.newBooleanLiteral(element.getObjectType() == GspElementObjectType.Enum);
        argumentList.add(paramEnum);

        var paramBelongElement = ast.newStringLiteral();
        paramBelongElement.setLiteralValue(belongElement == null ? "" : belongElement.getLabelID());
        argumentList.add(paramBelongElement);


        var paramParent = ast.newBooleanLiteral(isParentColumn(element));
        argumentList.add(paramParent);

        return argumentList;
    }

    public static GspDbDataType transDataType(DataType dataType) {
        GspDbDataType dbDataType = GspDbDataType.VarChar;
        switch (dataType) {
            case Char:
                dbDataType = GspDbDataType.Char;
                break;
            case Boolean:
                dbDataType = GspDbDataType.Boolean;
                break;
            case Varchar:
                dbDataType = GspDbDataType.VarChar;
                break;
            case Blob:
                dbDataType = GspDbDataType.Blob;
                break;
            case DateTime:
                dbDataType = GspDbDataType.DateTime;
                break;
            case TimeStamp:
                dbDataType = GspDbDataType.DateTime;
                break;
            case Clob:
                dbDataType = GspDbDataType.Clob;
                break;
            case Int:
            case SmallInt:
                dbDataType = GspDbDataType.Int;
                break;
            case Decimal:
                dbDataType = GspDbDataType.Decimal;
                break;
            case NChar:
                dbDataType = GspDbDataType.NChar;
                break;
            case NVarchar:
                dbDataType = GspDbDataType.NVarChar;
                break;
            case NClob:
                dbDataType = GspDbDataType.NClob;
                break;
        }
        return dbDataType;
    }

    private Expression getEnumTypeProcesser(MediateType enumType) {

        var vocation = ast.newClassInstanceCreation();
        vocation.setType(ast.newSimpleType(ast.newName(Enum2IntProcesser.class.getName())));
        var arg = ast.newTypeLiteral();
        arg.setType(ast.newSimpleType(ast.newName(enumType.getFullName())));
        vocation.arguments().add(arg);
        return vocation;
    }

    private java.lang.Class getTypeProcesser(
            GspDbDataType dbType, IGspCommonElement element) // GSPElementDataType elementType)
    {
        if (element.getObjectType() == GspElementObjectType.Enum) {
            switch (dbType) {
                case Int:
                    return Enum2IntProcesser.class;
                case VarChar:
                case NVarChar:
                    return Enum2VarcharProcesser.class;
            }

            throw new RuntimeException(
                    "#GSPBefError#" + "不支持数据库字段类型为【" + dbType.toString() + "】的枚举字段使用" + "#GSPBefError#");
        }
        switch (element.getMDataType()) {
            case Boolean:
                if (dbType == GspDbDataType.Int) {
                    return Bool2IntProcesser.class;
                }
                if (dbType == GspDbDataType.Char) {
                    return Bool2CharProcesser.class;
                }
                break;
            case Date:
            case DateTime:
                return DateTimeTransProcesser.class;
            case Decimal:
                if (dbType == GspDbDataType.Decimal) {
                    return DecimalTransProcesser.class;
                }
                break;
            case Integer:
                if (dbType == GspDbDataType.Int) {
                    return IntTransProcesser.class;
                }
                break;
            case String:
                return VarcharTransProcesser.class;
            case Text:
                return ClobTransProcesser.class;
            case Binary:
                return BinaryTransProcesser.class;
        }

        throw new RuntimeException(
                "#GSPBefError#"
                        + "没有找到字段类型为"
                        + element.getMDataType().toString()
                        + "，数据库类型为"
                        + dbType.toString()
                        + "的转换器"
                        + "#GSPBefError#");
    }


    @Override
    protected ArrayList<Modifier.ModifierKeyword> getAccessModifiers() {
        return new ArrayList<Modifier.ModifierKeyword>(
                Arrays.asList(
                        new Modifier.ModifierKeyword[]{
                                Modifier.ModifierKeyword.PUBLIC_KEYWORD
                        }));
    }

    @Override
    protected String getMethodName() {
        return "initColumnInfo";
    }

    @Override
    protected ArrayList<ParameterInfo> getParameterCollection() {
        return new ArrayList<ParameterInfo>();
    }

    @Override
    protected TypeInfo getReturnType() {
        return new TypeInfo("void");
    }
}
